﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace RVBike
{
    /* Room Discovery Protocol
     * 
     * Using in local area network to find created room list.
     * Requester will broadcast command "CMD=QRI&IP=$(MY_IP)" then
     * servers with opened room should reply message "CMD=QueryRoomInfo&$(ArchieveRoomString)" to $(MY_IP).
     */
    class RVRDP
    {
        public delegate void NewRoomHandler(RVRoom room);

        private System.Windows.Forms.Timer _searchTimer;
        private Socket _rdpListener;
        private List<RVRoom> _roomList;
        private string _ip;

        public NewRoomHandler NewRoom { get; set; }

        public RVRDP(int searchTimeout)
        {
            _searchTimer = new System.Windows.Forms.Timer();
            _searchTimer.Enabled = true;
            _searchTimer.Interval = searchTimeout;
            _searchTimer.Tick += new System.EventHandler(StopSearch);

            _ip = NetworkUtility.MyIP();

            _roomList = new List<RVRoom>();
        }

        public void Close()
        {
            _searchTimer.Tick -= StopSearch;
            _searchTimer = null;
            _roomList = null;
        }

        public List<RVRoom> QueryRoom()
        {
            _rdpListener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            _rdpListener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _rdpListener.Bind(new IPEndPoint(IPAddress.Any, RVNetworkEnv.CLIENT_PORT));

            // Clear cached
            _roomList.Clear();

            SendQueryCommand();

            // Create thread and wait
            Thread searchWorker = new Thread(() => receiveMsg());
            searchWorker.Start();
            searchWorker.Join();

            return _roomList;
        }

        private void SendQueryCommand()
        {
            Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            IPEndPoint iep1 = new IPEndPoint(IPAddress.Broadcast, RVNetworkEnv.SERVER_PORT);

            string command = String.Format("{0}?{1}={2}&{3}={4}", 
                RVNetworkEnv.PACKET_MESSAGE, RVNetworkEnv.COMMAND, RVNetworkEnv.QUERY_ROOM_INFO, RVNetworkEnv.CLIENT_IP, _ip);
            byte[] data = Encoding.UTF8.GetBytes(command);

            sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            sock.SendTo(data, iep1);
            sock.Shutdown(SocketShutdown.Both);
            sock.Close();  
        }

        private void receiveMsg()
        {
            if (_rdpListener == null) return;

            IPEndPoint iep = new IPEndPoint(IPAddress.Any, RVNetworkEnv.CLIENT_PORT);
            EndPoint ep = (EndPoint)iep;

            byte[] data = new byte[1024];
            string message;
            try
            {
                while (true)
                {
                    int recv = _rdpListener.ReceiveFrom(data, ref ep);
                    message = Encoding.UTF8.GetString(data, 0, recv);
                    ParseMsg(message);
                }
            }
            catch (System.Net.Sockets.SocketException)
            {
                System.Diagnostics.Trace.WriteLine("Search Timedout");
            }
        }

        private void ParseMsg(string message)
        {
            // Convert string to key-value parameter pairs
            Dictionary<string, string> info = new Dictionary<string, string>();
            string[] parameters = message.Substring(2).Split('&'); // Skip packet type "M?"
            foreach (string s in parameters)
            {
                string[] keyVal = s.Split('=');
                info.Add(keyVal[0], keyVal[1]);
            }

            string command;
            if (!info.TryGetValue(RVNetworkEnv.COMMAND, out command))
            {
                // No command parameter ... invalid message
                return;
            }

            try
            {
                switch (command)
                {
                    case RVNetworkEnv.ROOM_INFO:  // RoomInfo
                        {
                            RVRoom room = new RVRoom(info);
                            _roomList.Add(room);
                            NewRoom(room);
                            break;
                        }
                }
            }
            catch
            {
                // Something wrong during processing information.
                // Just skip this message!
            }
        }

        private void StopSearch(object sender, System.EventArgs e)
        {
            _rdpListener.Shutdown(SocketShutdown.Both);
            _rdpListener.Close();
            _searchTimer.Enabled = false;
        }
    }
}
